﻿
using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Linq;
using System.Reflection.Emit;
using System.Text;

namespace UniversalApproach.Expands
{
    public static class ObjectExpand
    {
        /// <summary>
        /// 深度复制
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static T DeepCopy<T>(this T obj) where T : class, new()
        {
            if (obj == null)
                return default(T);

            if (obj is string || obj.GetType().IsValueType)
                return obj;

            var retrieval = Activator.CreateInstance(obj.GetType());
            var fields = obj.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
            foreach (var field in fields)
            {
                try
                {
                    field.SetValue(retrieval, DeepCopy(field.GetValue(obj)));
                }
                catch { }
            }

            return (T)retrieval;

        }

        /// <summary>
        /// List集合转成DataTable
        /// </summary>
        /// <typeparam name="T">模型类型</typeparam>
        /// <param name="dataList">List列表</param>
        public static DataTable ToDataTable<T>(this IList<T> dataList) where T : class, new()
        {
            if (dataList == null) return null;
            DataTable dt = new DataTable();
            Type type = typeof(T);//T的类型
            PropertyInfo[] infos = type.GetProperties();
            foreach (PropertyInfo info in infos)
            {
                Type fieldType = info.PropertyType;
                if ((fieldType.IsGenericType) && (fieldType.GetGenericTypeDefinition() == typeof(Nullable<>)))
                    fieldType = fieldType.GetGenericArguments()[0];
                DataColumn dc = new DataColumn(info.Name, fieldType);
                dc.AllowDBNull = true;
                dt.Columns.Add(dc);
            }
            //转换数据
            foreach (var t in dataList)
            {
                DataRow dr = dt.NewRow();
                foreach (PropertyInfo info in infos)
                {
                    object objVal = info.GetValue(t, null);
                    dr[info.Name] = objVal ?? DBNull.Value;
                }
                dt.Rows.Add(dr);//添加到行
            }
            return dt;
        }

        /// <summary>
        /// DataTable转成List集合
        /// </summary>
        /// <typeparam name="T">模型类型</typeparam>
        /// <param name="dt">列表</param>
        public static IList<T> ToList<T>(this DataTable dt) where T : class, new()
        {
            if (dt == null || dt.Rows.Count == 0) return null;
            IList<T> list = new List<T>();//定义集合
            Type type = typeof(T);//类型
            string tempName = "";
            foreach (DataRow dr in dt.Rows)
            {
                T t = new T();
                PropertyInfo[] propertys = t.GetType().GetProperties();//获得公共属性
                foreach (PropertyInfo pi in propertys)
                {
                    tempName = pi.Name;
                    if (dt.Columns.Contains(tempName))//检查DataTable是否包含此列
                    {
                        if (!pi.CanWrite) continue;//判断此属性是否有Setter
                        object value = dr[tempName];
                        if (value != DBNull.Value)
                            pi.SetValue(t, value, null);
                    }
                }
                list.Add(t);
            }
            return list;
        }

        /// <summary>
        /// 将数据赋值
        /// </summary>
        /// <param name="obj">待赋值数据</param>
        /// <param name="data">需要更新值的数据</param>
        public static void ObjectAssignment(object obj, Dictionary<string, object> data)
        {
            if (obj == null || data == null)
                return;

            var properties = obj.GetType().GetProperties();
            var needProperties = properties.Where(f => data.ContainsKey(f.Name));
            foreach (var prop in needProperties)
            {
                prop.SetValue(obj, data[prop.Name], null);
            }
        }

        #region 获取复杂模型的属性信息
        /// <summary>
        /// 获取对象的全部属性信息。
        /// </summary>
        /// <remarks>若对象为多层对象（对象属性含有其他对象），则会将子对象属性一起列出。</remarks>
        /// <param name="obj">原对象</param>
        /// <returns><see cref="Properties"/>一个数组</returns>
        public static List<Properties> GetPropertiesByDeep(this object obj)
        {
            return GetProperties(obj.GetType().GetProperties());
        }

        /// <summary>
        /// 递归调用:获取对象的所有属性
        /// </summary>
        /// <param name="propertyInfos"></param>
        /// <param name="fatherPropertyName"></param>
        /// <returns></returns>
        public static List<Properties> GetProperties(PropertyInfo[] propertyInfos, string fatherPropertyName = "")
        {
            var propertiesList = new List<Properties>();

            foreach (var item in propertyInfos)
            {
                var tmpData = new Properties();
                if (item.Name == "MemberType")
                {
                    continue;
                }


                if (item.PropertyType == typeof(string))
                {
                    tmpData.PropertyInfo = item;
                    tmpData.PropertyName = item.Name;
                    tmpData.IsObject = false;
                    tmpData.FatherPropertyName = fatherPropertyName;
                }
                //日期特殊处理
                else if (item.PropertyType == typeof(DateTime))
                {
                    tmpData.PropertyInfo = item;
                    tmpData.PropertyName = item.Name;
                    tmpData.IsObject = false;
                    tmpData.FatherPropertyName = fatherPropertyName;
                }
                else if (item.PropertyType == typeof(DateTimeOffset))
                {
                    tmpData.PropertyInfo = item;
                    tmpData.PropertyName = item.Name;
                    tmpData.IsObject = false;
                    tmpData.FatherPropertyName = fatherPropertyName;
                }
                //判断是否为对象。对象拥有多个属性
                else if (item.PropertyType.GetProperties().Length > 0)
                {
                    //递归调用获取对象属性
                    propertiesList.AddRange(GetProperties(item.PropertyType.GetProperties(), $"{fatherPropertyName}.{item.Name}"));
                    continue;
                }
                else
                {
                    tmpData.PropertyInfo = item;
                    tmpData.PropertyName = item.Name;
                    tmpData.IsObject = false;
                    tmpData.FatherPropertyName = fatherPropertyName;
                }

                propertiesList.Add(tmpData);
            }

            foreach (var prop in propertiesList)
            {
                var reg = new System.Text.RegularExpressions.Regex("^\\.");
                if (reg.IsMatch(prop.FatherPropertyName))
                {
                    prop.FatherPropertyName = prop.FatherPropertyName.Remove(0, 1);
                }
            }

            return propertiesList;
        }

        /// <summary>
        /// 获取对象属性值
        /// </summary>
        /// <param name="propertyInfo"></param>
        /// <param name="source"></param>
        /// <returns></returns>
        public static object GetValue(Properties propertyInfo, object source)
        {
            //如果是深层的对象属性
            if (propertyInfo.FullName.Contains("."))
            {
                var deepName = propertyInfo.FullName.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries);
                return GetValueByDeep(propertyInfo, source, deepName.ToList());
            }
            else
            {
                return propertyInfo.PropertyInfo.GetValue(source, null);
            }
        }

        /// <summary>
        /// 递归调用:获取属性的值
        /// </summary>
        /// <param name="properties"></param>
        /// <param name="source"></param>
        /// <param name="deepName"></param>
        /// <returns></returns>
        public static object GetValueByDeep(Properties properties, object source, List<string> deepName)
        {
            if (deepName.Count == 1)
            {
                return properties.PropertyInfo.GetValue(source, null);
            }
            else
            {
                var first = deepName.FirstOrDefault();
                var firstObjProperty = source.GetType().GetProperty(first);
                var nextSource = firstObjProperty.GetValue(source, null);
                deepName.RemoveRange(0, 1);
                return GetValueByDeep(properties, nextSource, deepName);
            }
        }

        /// <summary>
        /// <see cref="GetProperties"/><see cref="GetValue"/><see cref="GetValueByDeep"/>方法使用的数据模型
        /// </summary>
        public class Properties
        {
            /// <summary>
            /// 属性名称
            /// </summary>
            public string PropertyName { get; set; }

            /// <summary>
            /// 是否为对象
            /// </summary>
            public bool IsObject { get; set; }

            /// <summary>
            /// 属性类型
            /// </summary>
            public PropertyInfo PropertyInfo { get; set; }

            /// <summary>
            /// 默认为空，不为空后续加”.“
            /// </summary>
            public string FatherPropertyName { get; set; }

            /// <summary>
            /// 全名
            /// </summary>
            public string FullName
            {
                get
                {
                    if (string.IsNullOrEmpty(FatherPropertyName))
                    {
                        return PropertyName;
                    }
                    else
                    {
                        return $"{FatherPropertyName}.{PropertyName}";
                    }
                }
            }
        }

        #endregion

        #region 简单模型转换
        /// <summary>
        /// 使用放射的方法转换数据，针对简单模型
        /// </summary>
        /// <typeparam name="T">转换后数据<see cref="Type"/></typeparam>
        /// <param name="obj">源数据</param>
        /// <returns></returns>
        public static T ChangeModelForSingle<T>(this object obj) where T : new()
        {
            if (obj == null) return new T();

            var sourceProperties = obj.GetType().GetProperties();
            var changeObjProperties = typeof(T).GetProperties();

            var needProperties = sourceProperties.Where(s => changeObjProperties.Any(f => f.Name == s.Name)).ToList();
            if (needProperties.Count <= 0) return new T();

            var tmp = new T();
            foreach (var item in needProperties)
            {
                var changeProperty = changeObjProperties.FirstOrDefault(s => s.Name == item.Name);
                if (changeProperty == null) continue;
                changeProperty.SetValue(tmp, item.GetValue(obj));
            }
            return tmp;
        }

        /// <summary>
        /// 使用放射的方法转换数据，针对简单模型
        /// </summary>
        /// <typeparam name="T">源数据<see cref="Type"/></typeparam>
        /// <typeparam name="T2">转换后数据<see cref="Type"/></typeparam>
        /// <param name="obj">源数据</param>
        /// <returns></returns>
        public static List<T2> ChangeModelForList<T, T2>(this List<T> obj)
            where
            T2 : new()
            where
            T : new()
        {
            if (obj == null) return new List<T2>();

            var sourceProperties = typeof(T).GetProperties();

            var changeProperties = typeof(T2).GetProperties();

            var needProperties = sourceProperties.Where(s => changeProperties.Any(f => f.Name == s.Name)).ToList();
            if (needProperties.Count <= 0) return new List<T2>();

            var tmp = new List<T2>();

            foreach (var item in obj)
            {
                var tmpItem = new T2();
                foreach (var pro in needProperties)
                {
                    var changeProperty = changeProperties.FirstOrDefault(s => s.Name == pro.Name);
                    if (changeProperty == null) continue;
                    changeProperty.SetValue(tmpItem, pro.GetValue(item));
                }

                tmp.Add(tmpItem);
            }
            return tmp;
        } 
        #endregion
    }



    /// <summary>
    /// 使用高性能的基于Emit实现的将DataTable转换成泛型集合Lis<T>的方法
    /// </summary>
    public static class DataTableConvert
    {
        /// <summary>
        /// 把DataRow转换为对象的委托声明
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dataRecord"></param>
        /// <returns></returns>
        private delegate T Load<T>(DataRow dataRecord);

        /// <summary>
        /// 用于构造Emit的DataRow中获取字段的方法信息
        /// </summary>
        private static readonly MethodInfo getValueMethod = typeof(DataRow).GetMethod("get_Item", new Type[] { typeof(int) });

        /// <summary>
        /// 用于构造Emit的DataRow中判断是否为空行的方法信息
        /// </summary>
        private static readonly MethodInfo isDBNullMethod = typeof(DataRow).GetMethod("IsNull", new Type[] { typeof(int) });

        /// <summary>
        /// 使用字典存储实体的类型以及与之对应的Emit生成的转换方法
        /// </summary>
        private static Dictionary<Type, Delegate> rowMapMethods = new Dictionary<Type, Delegate>();

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static List<T> ToList<T>(DataTable dt)
        {
            List<T> list = new List<T>();
            if (dt == null)
                return list;

            //声明 委托Load<T>的一个实例rowMap
            Load<T> rowMap = null;


            //从rowMapMethods查找当前T类对应的转换方法，没有则使用Emit构造一个。
            if (!rowMapMethods.ContainsKey(typeof(T)))
            {
                DynamicMethod method = new DynamicMethod("DynamicCreateEntity_" + typeof(T).Name, typeof(T), new Type[] { typeof(DataRow) }, typeof(T), true);
                ILGenerator generator = method.GetILGenerator();
                LocalBuilder result = generator.DeclareLocal(typeof(T));
                generator.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes));
                generator.Emit(OpCodes.Stloc, result);

                for (int index = 0; index < dt.Columns.Count; index++)
                {
                    PropertyInfo propertyInfo = typeof(T).GetProperty(dt.Columns[index].ColumnName);
                    Label endIfLabel = generator.DefineLabel();
                    if (propertyInfo != null && propertyInfo.GetSetMethod() != null)
                    {
                        generator.Emit(OpCodes.Ldarg_0);
                        generator.Emit(OpCodes.Ldc_I4, index);
                        generator.Emit(OpCodes.Callvirt, isDBNullMethod);
                        generator.Emit(OpCodes.Brtrue, endIfLabel);
                        generator.Emit(OpCodes.Ldloc, result);
                        generator.Emit(OpCodes.Ldarg_0);
                        generator.Emit(OpCodes.Ldc_I4, index);
                        generator.Emit(OpCodes.Callvirt, getValueMethod);
                        generator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);
                        generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());
                        generator.MarkLabel(endIfLabel);
                    }
                }
                generator.Emit(OpCodes.Ldloc, result);
                generator.Emit(OpCodes.Ret);

                //构造完成以后传给rowMap
                rowMap = (Load<T>)method.CreateDelegate(typeof(Load<T>));
            }
            else
            {
                rowMap = (Load<T>)rowMapMethods[typeof(T)];
            }

            //遍历Datatable的rows集合，调用rowMap把DataRow转换为对象（T）
            foreach (DataRow info in dt.Rows)
                list.Add(rowMap(info));
            return list;

        }


    }
}
